﻿using System.Windows.Controls;
using System.Collections.Generic;
using System;
using System.Reflection;
using System.Windows;
using System.Globalization;

namespace ToyChat.BuiltinModules
{
    /// <summary>
    /// This class can be used to dynamically generate a set of check boxes for the specified flags enum <typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">The flags enum to generate an interface for</typeparam>
    public class FlagsSelector<T> : UserControl where T : IConvertible
    {
        private GroupBox gBox1 = new GroupBox();
        private StackPanel mainPanel = new StackPanel();
        private Type enumType = typeof(T);
        private Type underlyingType;
        private Dictionary<string, dynamic> values = new Dictionary<string, dynamic>();
        private bool isSigned = false;
        private T val;
        private List<CheckBox> boxes = new List<CheckBox>();
        private bool updating = false;

        /// <summary>
        /// Initializes a new instance of <see cref="FlagsSelector{T}"/> and generates a UI for the specified flags enum <typeparamref name="T"/>
        /// </summary>
        public FlagsSelector()
        {
            this.updating = true;
            if (!enumType.IsEnum)
                throw new InvalidOperationException("T Must be enumerable");
            bool found = false;
            foreach (object a in enumType.GetCustomAttributes(false))
                found |= a is FlagsAttribute;
            if (!found)
                throw new InvalidOperationException("T Must have flags attribute");

            this.underlyingType = Enum.GetUnderlyingType(this.enumType);
            this.isSigned = !this.underlyingType.Name.ToLower().StartsWith("u");
            string[] f = Enum.GetNames(this.enumType);
            foreach (string name in f)
            {
                if (this.isSigned)
                {
                    long val = ((T)Enum.Parse(this.enumType, name)).ToInt64(NumberFormatInfo.CurrentInfo);
                    if (val == 0)
                        continue;

                    try { this.values.Add(name, val); }
                    catch { }
                }
                else
                {
                    ulong val = ((T)Enum.Parse(this.enumType, name)).ToUInt64(NumberFormatInfo.CurrentInfo);
                    if (val == 0)
                        continue;

                    try { this.values.Add(name, val); }
                    catch { }
                }
            }

            this.gBox1.Header = this.enumType.Name;
            this.Content = this.gBox1;
            this.gBox1.Content = this.mainPanel;
            this.Value = default(T);
            this.GenerateUI();
            this.updating = false;
        }

        /// <summary>
        /// Called to generate the actual check boxes
        /// </summary>
        private void GenerateUI()
        {
            //foreach (string fieldName in fields)
            this.boxes.Clear();
            foreach (KeyValuePair<string, dynamic> kvp in this.values)
            {
                CheckBox cb = new CheckBox();
                cb.Margin = new Thickness(4);
                cb.Content = kvp.Key;
                cb.Name = kvp.Key;
                cb.Tag = kvp.Value;
                cb.Click += new RoutedEventHandler(cb_Click);
                this.mainPanel.Children.Add(cb);
                this.boxes.Add(cb);
            }
        }

        /// <summary>
        /// Gets or Sets the value of enum that this instance is displaying
        /// </summary>
        public T Value
        {
            get { return this.val; }
            set
            {
                bool oldUpdating = this.updating;
                this.updating = true;
                this.val = (T)Enum.Parse(this.enumType, value.ToString());
                foreach (CheckBox box in this.boxes)
                {
                    if (this.isSigned)
                    {
                        long tag = (long)box.Tag;
                        box.IsChecked = (this.val.ToInt64(NumberFormatInfo.CurrentInfo) & tag) == tag;
                    }
                    else
                    {
                        ulong tag = (ulong)box.Tag;
                        box.IsChecked = (this.val.ToUInt64(NumberFormatInfo.CurrentInfo) & tag) == tag;
                    }
                }
                this.updating = oldUpdating;
            }
        }

        /// <summary>
        /// Called when a checkbox has been clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void cb_Click(object sender, RoutedEventArgs e)
        {
            if (!this.updating)
            {
                CheckBox box;
                if ((box = sender as CheckBox) != null)
                {
                    if (box.IsChecked == true)
                    {
                        if (this.isSigned)
                        {
                            long tag = (long)box.Tag;
                            long v = this.val.ToInt64(NumberFormatInfo.CurrentInfo);
                            v |= tag;
                            try { this.Value = (T)Enum.Parse(this.enumType, v.ToString()); }
                            catch { }
                        }
                        else
                        {
                            ulong tag = (ulong)box.Tag;
                            ulong v = this.val.ToUInt64(NumberFormatInfo.CurrentInfo);
                            v |= tag;
                            try { this.Value = (T)Enum.Parse(this.enumType, v.ToString()); }
                            catch { }
                        }
                    }
                    else
                    {
                        if (this.isSigned)
                        {
                            long tag = (long)box.Tag;
                            long v = this.val.ToInt64(NumberFormatInfo.CurrentInfo);
                            v = (v | tag) ^ tag;
                            try { this.Value = (T)Enum.Parse(this.enumType, v.ToString()); }
                            catch { }
                        }
                        else
                        {
                            ulong tag = (ulong)box.Tag;
                            ulong v = this.val.ToUInt64(NumberFormatInfo.CurrentInfo);
                            v = (v | tag) ^ tag;
                            try { this.Value = (T)Enum.Parse(this.enumType, v.ToString()); }
                            catch { }
                        }
                    }
                }
            }
        }
    }
}